<?php
/**
 * Copyright (c) 2006-2007, Julien PORTALIER
 *
 * Some functions are derived from CakePHP,
 * Copyright (c) 2006, Cake Software Foundation, Inc.
 * @link http://cakephp.org/
 * 
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 * 
 * @copyright    Copyright (c) 2006-2007, Julien PORTALIER
 * @link         http://featherphp.googlecode.com/
 * @license      http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Manipulates XHTML tags.
 */
class XhtmlHelper extends Helper
{
	protected $_tags = array('area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta', 'option', 'param');
	protected $validationErrors = array();
	
	function __construct(View $view=null)
	{
		parent::__construct($view);
		
		if (isset($view->controller))
		{
			$modelClass = $view->controller->modelClass;
			if (isset($view->controller->{$modelClass}, $view->controller->{$modelClass}->validationErrors))
				$this->validationErrors =& $view->controller->{$modelClass}->validationErrors;
		}
	}
	
	// header
	
	function doctype($doctype)
	{
		switch($doctype)
		{
			// XHTML 1.1
			case "xhtml11":         return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
			
			// XHTML 1.0
			case "xhtml1-strict":   return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
			case "xhtml1-trans":    return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
			case "xhtml1-frameset": return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
			
			// HTML 4.01
			case "html4-strict":    return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">';
			case "html4-trans":     return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
			case 'html4-frameset':  return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">';
			
			default:
				trigger_error("Unknown doctype: ".$doctype, E_USER_ERROR);
		}
	}
	
	function html($namespace, $lang=null)
	{
		switch($namespace)
		{
			// XHTML
			case "xhtml11":
			case "xhtml1-strict":
			case "xhtml1-trans":
			case "xhtml1-frameset":
				return '<html xmlns="http://www.w3.org/1999/xhtml"'.(empty($lang) ? null : ' xml:lang="'.$lang.'"').'>';
			// HTML
			default:
				return '<html'.(empty($lang) ? null : ' lang="'.$lang.'"').'>';
		}
	}
	
	function meta($name, $content)
	{
		return $this->tag('meta', array('name' => $name, 'content' => $content));
	}
	
	function charset($content_type='text/html', $charset=null)
	{
		return $this->tag('meta', array(
			'http-equiv' => 'Content-Type',
			'content'    => $content_type.'; charset='.(empty($charset) ? Core::charset : $charset)
		));
	}
	
	function css($url, $media=null, $attr=null)
	{
		if (strpos($url, 'http://') !== 0)
		{
			if ($url{0} != '/')
				$url = "/css/$url";
			if (Core::compress_css)
			{
				#$attr['href'] = "/css.php?url=$url.css";
				$attr['href'] = "/c/$url.css";
			}
			else {
				$attr['href'] = "$url.css";
			}
			
			$attr['href'] .= '?'.Core::version;
		}
		else {
			$attr['href'] = $url;
		}
		
		if (!empty($media))
			$attr['media'] = $media;
		$attr['rel']  = 'stylesheet';
		$attr['type'] = 'text/css';
		return $this->tag('link', &$attr);
	}
	
	function rss($title, $url, $rel='alternate', $attr=null)
	{
		$attr['rel']   = $rel;
		$attr['type']  = 'application/rss+xml';
		$attr['title'] = $title;
		$attr['href']  = $url;
		return $this->tag('link', &$attr);
	}
	
	
	// tags
	
	function fixRelativeLinks($html, $url_base)
	{
		return preg_replace(
			'{<([^<>]*)(href|src|data)="(?!(?:http[s]?|mailto|ftp[s]?):|#)([^>"]*)"}i',
			'<\1\2="'.$url_base.'\3"', $html);
	}
	
	function img($url, $alt='', $attr=null)
	{
		$attr['src'] = $this->url($url);
		$attr['alt'] = $alt;
		return $this->tag('img', &$attr);
	}
	
	function linkEmail($name, $email)
	{
		return $this->contentTag('a', $name, array('href' => "mailto:$email"));
	}
	
	
	function & arrayLinks(array $list, $baseUrl, array $attr=null)
	{
		foreach($list as $i => $v)
			$list[$i] = $this->link($v, $baseUrl.$v, $attr);
		return $list;
	}
	
	// forms
	
	function formTag($action, $method='post', $attr=null)
	{
		$attr['action'] = $this->url($action);
		$attr['method'] = $method;
		return '<form '.$this->_parseAttributes(&$attr).'>';
	}
	
	function fieldset($legend, $content)
	{
		return $this->contentTag('fieldset',
			$this->contentTag('legend', $legend).$content);
	}
	
	function label($label, $for=null, $title=null, $attr=null)
	{
		if (!empty($title))
			$attr['title'] = $title;
		if (!empty($for))
		{
			$for = isset($attr['id']) ? $attr['id'] : $this->__getIdFromField($for);
			if (!empty($for))
			{
				$attr['for'] = $for;
				$attr['id']  = "l_$for";
			}
		}
		return $this->contentTag('label', $label, &$attr);
	}
	
	function textarea($name, $attr=null)
	{
		$value = $this->__tagValue($name, &$attr);
		$attr['name'] = $this->__tagName($name);
		
		if (empty($attr['id']))
		{
			$id = $this->__getIdFromField($name);
			if (!empty($id))
				$attr['id'] = $id;
		}
		
		if (!isset($attr['cols']))
			$attr['cols'] = 60;
		
		if (!isset($attr['rows']))
			$attr['rows'] = 10;
		
		return $this->contentTag('textarea', $value, &$attr);
	}
	
	function input($type='text', $name, $attr=null, $error=true)
	{
		if (!isset($attr['value']) and $type != 'checkbox' and $type != 'file' and $type != 'radio')
			$attr['value'] = $this->__tagValue($name, &$attr);
		$attr['name'] = $this->__tagName($name);
		
		# special types
		switch($type)
		{
			case 'datetime':
				if (empty($attr['value']))
					$attr['value'] = date('Y-m-d H:i:s');
				$attr['type']      = 'text';
				$attr['class']     = 'datetime';
				$attr['maxlength'] = 19;
				break;
			
			case 'date':
				if (empty($attr['value']))
					$attr['value'] = date('Y-m-d');
				$attr['type']      = 'text';
				$attr['class']     = 'date';
				$attr['maxlength'] = 10;
				break;
			
			case 'time':
				if (empty($attr['value']))
					$attr['value'] = date('H:i:s');
				$attr['type']      = 'text';
				$attr['class']     = 'time';
				$attr['maxlength'] = 8;
				break;
			
			case 'radio':
				$attr['type'] = $type;
				$value = $this->__tagValue($name);
				
				# note: 'strlen($value) != 0' is needed when $value is string '0'
				if ($value == $attr['value'] and strlen($value) != 0)
					$attr['checked'] = 'checked';
				break;
			
			default:
				if (!isset($attr['maxlength']))
				{
					# looking for input field's max length (is available)
					$modelClass = (strpos($name, '/') !== false) ? explode('/', $name) : null;
					if ($modelClass)
					{
						list($modelClass, $field) = $modelClass;
						$model = $this->controller->load->model($modelClass);
						
						if (isset($model->fields, $model->fields[$field]['length']))
							$attr['maxlength'] = $model->fields[$field]['length'];
					}
				}
				$attr['type'] = $type;
		}
		
		# id
		if (empty($attr['id']))
		{
			$id = $this->__getIdFromField($name);
			if (!empty($id))
				$attr['id'] = $id;
		}
		
		# CSS class
		if (empty($attr['class']))
			$attr['class'] = $type;
		
		return $this->tag('input', &$attr);
	}
	
	function checkbox($name, $attr=null)
	{
		if (!isset($attr['checked']) and $this->__tagValue($name))
			$attr['checked'] = 'checked';
		return self::input('checkbox', $name, $attr, false);
	}
	
	function radio($name, $options, $in_between=null, $attr=null)
	{
		$attr['name'] = $this->__tagName($name);
		$selected     = $this->__tagValue($name, &$attr);
		
		$content = '';
		$i  = 0;
		$id = 'tag_'.preg_replace('/[^\w\d-_.]+/', '-', $name);
		$o_attributes = array(
			'type'  => 'radio',
			'name'  => $attr['name'],
			'tabindex' => $attr['tabindex'],
			'class' => 'radio',
		);
		
		foreach ($options as $value => $label)
		{
			$i++;
			$o_attributes['value'] = $value;
			$o_attributes['id']    = "{$id}_{$i}";
			unset($o_attributes['checked']);
			
			if ($value == $selected)
				$o_attributes['checked'] = 'checked';
			
			$content .= $this->tag('input', &$o_attributes);
			$content .= $this->label($label, $o_attributes['id']);
			$content .= $in_between;
		}
		
		return $this->contentTag('span', $content, array('class' => 'radios'));
	}
	
	function select($name, $options, $attr=null)
	{
		if (!empty($options))
		{
			$selected = $this->__tagValue($name, &$attr);
			$content  = '';
			
			foreach ($options as $value => $label)
			{
				$o_attributes = array();
				$o_attributes['value'] = $value;
				
				if ($value == $selected)
					$o_attributes['selected'] = 'selected';
				
				$content .= $this->contentTag('option', $label, &$o_attributes); 
			}
		}
		else {
			$content = $this->contentTag('option', null);
		}
		
		if (empty($attr['id']))
		{
			$id = $this->__getIdFromField($name);
			if (!empty($id))
				$attr['id'] = $id;
		}
		$attr['name'] = $this->__tagName($name);
		
		unset($attr['value']);
		
		return $this->contentTag('select', $content, &$attr);
	}
	
	function selectMany($name, $options, $attr=null)
	{
		if (!empty($options))
		{
			list($model, $field) = explode('/', $name);
			$selected = empty($this->data[$model]) ?
				array($model => array()) : $this->data[$model];
			
			$content = '';
			foreach ($options as $value => $label)
			{
				$o_attributes = array();
				$o_attributes['value'] = $value;
				
				if (in_array($value, $selected))
					$o_attributes['selected'] = 'selected';
				
				$content .= $this->contentTag('option', $label, &$o_attributes); 
			}
		}
		else {
			$content = $this->contentTag('option', null);
		}
		
		$attr['name'] = $this->__tagName($name);
		$attr['multiple'] = 'multiple';
		if (!isset($attr['size']))
			$attr['size'] = 8;
		
		return $this->contentTag('select', $content, &$attr);
	}
	
	function submit($label, $name=null, $attr=null)
	{
		$attr['value'] = $label;
		$attr['type']  = 'submit';
		if (!empty($name))
			$attr['name']  = $name;
		
		if (empty($attr['class']))
			$attr['class'] = 'submit';
		
		return $this->tag('input', &$attr);
	}
	
	// errors
	
	function errorMessage($field)
	{
		if (strpos($field, '/'))
		{
			list($model, $field) = explode('/', $field);
			if (isset($this->validationErrors[$model][$field]))
			{
				$error = $this->validationErrors[$model][$field];
				return $this->contentTag('strong', 
					'&#8594; '.(empty($error) ? _('Error in field.') : $error),
					array('class' => 'message')
				);
			}
		}
		return null;
	}
	
	function hasErrors($text=null)
	{
		if (!empty($this->validationErrors))
		{
			$text = empty($text) ? _("Please correct errors before proceding.") : $text;
			return $this->contentTag('p', "<strong>$text</strong>", array('class' => 'error_report'));
		}
	}
	
	// internals
	
	protected function __getIdFromField($field)
	{
		if (!strpos($field, '/'))
			return $field;
		return Inflector::variablize(str_replace('/', '_', $field));
	}
	
	protected function __tagValue($field, $attr=null)
	{
		if (empty($attr['value']))
		{
			if (strpos($field, '/'))
			{
				list($model, $field) = explode('/', $field);
				return isset($this->data[$model][$field]) ? htmlspecialchars($this->data[$model][$field]) : false;
			}
		}
		else {
			return htmlspecialchars($attr['value']);
		}
	}
	
	protected function __tagName($field)
	{
		if (strpos($field, '/') !== false)
		{
			list($model, $field) = explode('/', $field);
			return "data[$model][$field]";
		}
		return $field;
	}
}
?>